package nl.sogyo.objectvisualisatie.core;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import java.util.TreeMap;

import nl.sogyo.objectvisualisatie.data.MethodInfo;
import nl.sogyo.objectvisualisatie.data.ObjectInfo;
import nl.sogyo.objectvisualisatie.data.SourceCode;
import nl.sogyo.objectvisualisatie.data.SourceFileLoader;

import com.sun.jdi.Field;
import com.sun.jdi.Location;
import com.sun.jdi.Method;


public class DataFacade {
	
	private Model model;
	private ArrayList<MethodInfo> methodCallsList;				// ArrayList with method-information
	private ArrayList<ObjectInfo> objectsList;					// ArrayList with object-information
	private Stack<ObjectInfo> objectStack;						// Stack with ObjectInfo: Stack keeps track which object does a function call
	private SourceFileLoader sourceFileLoader;					// Keeps track of all the loaded sourceCode
	
	private int sleepTime = 200;								// Time to pause after a method call 
	private boolean threadContinue = true;						// Pause the monitoring of the application
	private boolean next = true;								// Execute one step (true/false)
	private Thread interruptThread;								// Thread to pause for pausing the execution
	
	/**
	 * Contructor creates the most important fields for managing
	 * the data.
	 * @param model
	 */
	public DataFacade(Model model)
	{
		this.model = model;
		methodCallsList = new ArrayList<MethodInfo>();
		objectsList = new ArrayList<ObjectInfo>();		
		objectStack = new Stack<ObjectInfo>();		
		sourceFileLoader = new SourceFileLoader();
	}
	
	 /**
	  * process a executed method call
	  * @param method 		= method that has been executed
	  * @param onObjectName = classname of the object on wich this method was executed
	  * @param id 			= unique id of the object
	  */
	public void procesMethodcall(Method method, String onObjectName, int id) {
		interruptThread = Thread.currentThread();
		while(threadContinue == false)
		{
			if(next == true)break;
			try {
				Thread.sleep(60000);
			} catch (InterruptedException e) {
//				  No exception 
//				  e.printStackTrace();
			}
		}
		setNext(false);
		try {
			Thread.sleep(sleepTime);
		} catch (InterruptedException e) {
//			No exception 
//			e.printStackTrace();
		}
		
		ObjectInfo fromObject = objectStack.lastElement();
		
		// Find the corresponding object for the id retrieve
		ObjectInfo onObject = null;
		for(ObjectInfo objectinfo : objectsList){
			if(objectinfo.getId() == id)
			{
				onObject = objectinfo;
				break;
			}
		}
		addMethodcall(method, fromObject, onObject); // add method call
		pushObjectStack(onObject); //add object on stack
		model.notifyObs(); //update GUI
	}
	
	/**
	 * This method will be called when a new object is created
	 * within the virtual machine. This method takes covers all 
	 * the tasks for creating a new ObjectInfo object for the 
	 * new object that is just created.
	 * 
	 * @param id = the id of the created object
	 * @param name = classname of the object
	 * @param methods = methods that this object has
	 * @param fields = all the fields this object has
	 */
	public void objectInit(int id, String name, List<Method> methods, List<Field> fields)
	{
		ObjectInfo fromObject = objectStack.lastElement();
		fromObject.addInitialisation(id);
		
		ObjectInfo objectInfo = new ObjectInfo(name, id); // create new object
		objectInfo.setMethods(methods);
		
		for(Field field: fields)
		{			
			objectInfo.addField(field, null);
		}
		addObject(objectInfo);
	}
	
	/**
	 * Save method that is executed
	 * @param fromObject 	= the object that calls the method)
	 * @param call			= method that has been executed
	 * @param onObject		= the object on which the function is called
	 */
	public void addMethodcall(Method method, ObjectInfo fromObject, ObjectInfo onObject)
	{
		MethodInfo methodInfo = new MethodInfo(fromObject, method, onObject);
		methodCallsList.add(methodInfo);
	}
	
	/**
	 * 
	 * @return
	 */
	public ArrayList<MethodInfo> getMethodcalls()
	{
		return methodCallsList;
	}
	
	/**
	 * A new object has been created and wil be added
	 * to list of all objects that have been created
	 * @param objectInfo = new object that has been created
	 */
	public void addObject(ObjectInfo objectInfo) {
		objectsList.add(objectInfo);
		model.notifyObs();
	}
	
	public ArrayList<ObjectInfo> getobjectsList()
	{
		return objectsList;
	}
	
	/**
	 * Push an objectInfo object on the stack, the
	 * top of the stack will be the object where the tread
	 * is right now
	 * @param objectInfo = objectInfo were the current thread is.
	 */
	public void pushObjectStack(ObjectInfo objectInfo)
	{
		objectStack.push(objectInfo);
	}
	
	/**
	 * Remove an objectInfo object from the stack
	 * meanin that an method is exited
	 */
	public void popObjectStack()
	{
		objectStack.pop();
	}
	
	/**
	 * 
	 * @return
	 */
	public Stack<ObjectInfo> getObjectStack()
	{
		return objectStack;
	}
	
	/**
	 * add a relation to an object
	 * @param id = the id of the objectInfo object which has a relation with this object 
	 */
	public void addRelation(int id)
	{
		for(ObjectInfo objectinfo : objectsList){
			if(objectinfo == objectStack.lastElement())
			{
				objectinfo.addRelation(id);
				break;
			}
		}		
	}
	
	/**
	 * Check if all relations of an object are still correct. All relation  
	 * were removed and will be readded to the current object
	 * @param objectid		= unique objectid of the object that has relation with relationid
	 * @param relationid	= unique objectid of the object that is a relation of objectid
	 */
	public boolean reAddRelation(int objectid, int relationid)
	{
		for(ObjectInfo objectinfo : objectsList){
			if(objectinfo.getId() == objectid)
			{
				for(ObjectInfo objectinfo2 : objectsList){
					if(objectinfo2.getId() == relationid)
					{
						objectinfo.addRelation(relationid);
						return true;
					}
				}
				break;
			}
		}
		return false;				
	}
	
	/**
	 * Remove all relation of an object, after this method
	 * readdRelation will be probably called
	 * @param id = unique objectid that all relation will be removed
	 */
	public void removeRelations(int id)
	{
		for(ObjectInfo objectinfo : objectsList){
			if(objectinfo.getId() == id)
			{
				objectinfo.removeRelations();
				break;
			}
		}	
	}
	
	/**
	 * Add a field and value to a current object is called
	 * when a value of the object fields are changed
	 * @param field = the field of an object
	 * @param value = the value of the field above
	 */
	public void addField(Field field, Object value)
	{
		for(ObjectInfo objectinfo : objectsList){
			if(objectinfo == objectStack.lastElement())
			{
				objectinfo.addField(field, value);
				break;
			}
		}		
	}
	
	/**
	 * Add a new field/value to a current object 
	 * @param objectId 	= id of the object
	 * @param field		= field that will be added
	 * @param value		= value of field
	 */
	public void addFieldId(int objectId, Field field, Object value)
	{
		for(ObjectInfo objectinfo : objectsList){
			if(objectinfo.getId() == objectId)
			{
				objectinfo.addField(field, value);
				break;
			}
		}		
	}
		
	public boolean isThreadContinue() {
		return threadContinue;
	}

	public void setThreadContinue(boolean bool) {
		this.threadContinue = bool;	
		if(bool == true)
		{
			interruptThread.interrupt();
		}
	}
	
	public void setNext(boolean bool) {
		this.next = bool;
		if(bool == true && interruptThread != null)
		{
			interruptThread.interrupt();
		}	
	}
	
	/**
	 * Remove all information that is gathered from the application
	 */
	public void removeAll()
	{
		methodCallsList.clear();					
		objectsList.clear();				
		objectStack.clear();
		sourceFileLoader.clearTreeMap();
		model.notifyObs();
	}
	
	/**
	 * add a java source file to the list of all sourcefiles
	 * @param fnm = filename of java source file
	 */
	public void addSourceFile(String fnm)
	{
		sourceFileLoader.add(model.getSourcepath(), fnm);
	}
	
	/**
	 * Show a current line of source code
	 * @param fnm = filename of the sourcefile
	 * @param loc = linenumber of the sourcefile wich code is executed
	 */
	public void showCode(String fnm, Location loc)
	{
		  sourceFileLoader.showLine(fnm, loc.lineNumber());
		  model.notifyObs();
	}
	
	public TreeMap<String, SourceCode> readAllSourcefiles()
	{		
		return sourceFileLoader.getTreeMap();
	}
	
	/**
	 * Update GUI through model
	 */
	public void callUpdate()
	{
		model.notifyObs();
	}
}
